﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using COFE.Core.Lister;
using COFE.Core.Utils;
using COFE.Core.Profile;
using System.Dynamic;

namespace COFE.Core.Entry
{
    public abstract class PropertyHostBase<T> : DynamicObject, IPropertyHost<T>     
    {
        #region Constructor

        protected PropertyHostBase(IPropertyProvider primaryPropProvider)
        {            
            this.PrimaryPropertyProvider =
                (primaryPropProvider == null) ?
                new NullPropertyProvider() : primaryPropProvider;
        }

        #endregion

        #region Methods

        #region Property related
        private void checkThisObj()
        {
            if (_thisObj == null)
                throw new Exception("_thisObj is not set by child implementer.");
        }

        public bool IsPropertySupported(object property, 
            PropertyRetrivalOptions options = PropertyRetrivalOptions.NotApplied)
        {
            checkThisObj();
            return GetSupportedProperty(options).Contains(property);
        }

        public virtual object[] GetSupportedProperty(
            PropertyRetrivalOptions options = PropertyRetrivalOptions.NotApplied,
            MatchMode matchMode = MatchMode.All,             
            params IPropertyFilter[] filters)
        {
            checkThisObj();
            if (_supportedProperty == null) //If not cached.
            {
                List<object> retList = new List<object>();

                #region addProperty, addSecProperty
                Action<IPropertyProvider> addProperty = (pp) =>
                {
                    foreach (var p in pp.GetSupportedProperty())
                        if (!retList.Contains(p))
                            retList.Add(p);
                };

                Action<ISecondaryPropertyProvider<T>> addSecProperty = (pp) =>
                {
                    foreach (var p in pp.GetSupportedProperty(_thisObj))
                        if (!retList.Contains(p))
                            retList.Add(p);
                };
                #endregion

                addProperty(PrimaryPropertyProvider);
                foreach (var secProvider in
                    COFEShared.SecondaryPropertyProviderManager
                    .GetSecondaryPropertyProviders<T>())
                    addSecProperty(secProvider);

                var propWithdefValues = COFEShared.PropertyDictionary
                    .GetPropertiesStringWithDefaultValue();

                _supportedProperty = retList.Union(propWithdefValues).Distinct().ToArray();
            }

            return (from p in _supportedProperty
                    where FilterUtils2.MatchPropertyFilters(p, matchMode, filters)
                    select p).ToArray();            
        }

        public virtual PropertyPair GetProperty(object property,
            PropertyRetrivalOptions options = PropertyRetrivalOptions.NotApplied)
        {
            checkThisObj();
            if (property is string)
                return IPropertyHostExtension.GetProperty(
                    this, property as string, options);

            //Special implementation for resource list.
            var rla = EnumUtils<ResourceListAttribute>.FindAttribute(property);
            if (rla != null)
            {
                if (IsPropertySupported(property, options))
                    return new PropertyPair(property, 
                        new ResourceList(this, property, options));
                else throw new NotSupportedException();
            }

            #region testProvider, testSecProvider
            Func<IPropertyProvider, object, bool> testProvider = (pp, prop) =>
            {
                if (pp.IsPropertySupported(prop))
                    return true;
                return false;
            };
            Func<ISecondaryPropertyProvider<T>, object, bool> testSecProvider = (pp, prop) =>
            {
                if (pp.IsPropertySupported(_thisObj, prop))
                    return true;
                return false;
            };
            #endregion

            if (testProvider(PrimaryPropertyProvider, property))
                return PrimaryPropertyProvider.GetProperty(property);

            if (this is DirectoryInfoExA && 
                this.IsPropertySupported(DirectoryInfoProperties.FileBasedFSFile))
            {
                var FBFSFile = this.GetProperty(
                    DirectoryInfoProperties.FileBasedFSFile, options).Value as 
                    IInternalFileSystemInfoExA;
                if (FBFSFile.IsPropertySupported(property))
                    return FBFSFile.GetProperty(/* options, */property);
            }

            foreach (var secProvider in COFEShared
                .SecondaryPropertyProviderManager
                .GetSecondaryPropertyProviders<T>())
                if (testSecProvider(secProvider, property))
                    return secProvider.GetProperty(_thisObj, property);

            //See if the property has default value.
            if (COFEShared.PropertyDictionary.HasDefaultValue(property))
                return COFEShared.PropertyDictionary.GetDefaultValuePP(property);

            throw new KeyNotFoundException(String.Format("{0} is not supported in {1}", property.ToString(), PrimaryPropertyProvider.GetType()));
        }

        public virtual void SetProperty(object property, object value,
            PropertyRetrivalOptions options = PropertyRetrivalOptions.NotApplied)
        {
            checkThisObj();
            if (property is string)
            {
                IPropertyHostExtension.SetProperty(
                    this, property as string, value);
                return;
            }

            #region testProvider, testSecProvider
            bool propFound = false;
            Func<IPropertyProvider, object, bool> testProvider = (pp, prop) =>
            {
                if (pp.IsPropertySupported(prop))
                {
                    propFound = true;
                    if (!pp.IsPropertyReadOnly(prop))
                        return true;
                }
                return false;
            };
            Func<ISecondaryPropertyProvider<T>, object, bool> testSecProvider = (pp, prop) =>
            {
                if (pp.IsPropertySupported(_thisObj, prop))
                {
                    propFound = true;
                    if (!pp.IsPropertyReadOnly(_thisObj, prop))
                        return true;
                }
                return false;
            };
            #endregion

            bool propSet = false;
            //If is cached property, set the cache first.
            if (PrimaryPropertyProvider is IPropertyProviderCache 
                || testProvider(PrimaryPropertyProvider, property))
            {
                PrimaryPropertyProvider.SetProperty(property, value);
                if (!(PrimaryPropertyProvider is IPropertyProviderCache))
                    propSet = true;
            }

            //Check SecondaryProvider.
            foreach (var secProvider in COFEShared.SecondaryPropertyProviderManager
                .GetSecondaryPropertyProviders<T>())
                if (testSecProvider(secProvider, property))
                {
                    secProvider.SetProperty(_thisObj, property, value);
                    propSet = true;
                }

            IPropertyProvider primaryPP = PrimaryPropertyProvider;

            //If this is entry and PrimaryPropertyProvider is not implemented (null),
            //Try parse the path and return it's actual primary property provider.            
            if (!propSet && 
                _thisObj is IFileSystemInfo &&
                primaryPP is NullPropertyProvider)
            {
                string parseName = (_thisObj as IFileSystemInfo).ParseName;
                var lookupEntry = COFEShared.PathParser
                    .TryParsePath(parseName, ParsableType.ParsePath) 
                    as IInternalFileSystemInfoExA;
                if (lookupEntry == null)
                    throw new System.IO.FileNotFoundException(parseName);
                primaryPP = lookupEntry.PrimaryPropertyProvider;
            }

            //Check primary property provider.
            if (testProvider(primaryPP, property))
                primaryPP.SetProperty(property, value);

            if (!propFound)
                throw new KeyNotFoundException(String.Format("{0} is not supported in {1}",
                    property, PrimaryPropertyProvider.GetType()));
            if (!propSet)
                throw new NotSupportedException(String.Format("{0} does not support write.", 
                    property));
        }
        #endregion

        #region Dynamic related

        public override IEnumerable<string> GetDynamicMemberNames()
        {
            foreach (string name in this.GetSupportedPropertyString())
                yield return name;

            foreach (string name in base.GetDynamicMemberNames())
                yield return name;
        }

        public override bool TryGetMember(GetMemberBinder binder, out object result)
        {
            try
            {
                PropertyPair ppair = this.GetProperty(binder.Name);
                if (ppair != null)
                {                    
                    result = ppair.Value;
                    return true;
                }
            }
            catch (KeyNotFoundException)
            {
                return base.TryGetMember(binder, out result);
            }

            result = null;
            return false;
        }


        #endregion

        #endregion

        #region Data

        protected T _thisObj = default(T);
        private object[] _supportedProperty = null;

        #endregion

        #region Public Properties

        public IPropertyProvider PrimaryPropertyProvider { get; private set; }
        public dynamic DynamicProperties { get { return this as dynamic; } }

        #endregion





       
    }
}
